package com.paic.common.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ReflectionUtil {

    private static final Logger log = LoggerFactory.getLogger(ReflectionUtil.class);

    /**
     * Whether found class
     *
     * @param className target class name
     * @return if found return true, else return false
     */
    public static boolean classFound(String className) {
        try {
            Class.forName(className);
            return true;
        } catch (Exception e) {
        }

        return false;
    }

    /**
     * Whether found classes
     *
     * @param classNames target classes names
     * @return if all classes found return true, else return false
     */
    public static boolean classFound(String[] classNames) {
        try {
            if (!StringUtil.isEmpty(classNames)) {
                for (String className : classNames) {
                    classFound(className);
                }
            }
            return true;
        } catch (Exception e) {
        }

        return false;
    }

    public static Class<?> getClass(String className) {
        try {
            return Class.forName(className);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Return the target bean information
     *
     * @param t target bean
     * @return the information about all fields
     * @throws Exception
     */
    public static <T> String getTargetInfo(T t) {
        Field[] fields = t.getClass().getDeclaredFields();

        StringBuffer sb = new StringBuffer("[" + t.getClass().getName() + ":");

        try {
            for (Field field : fields) {
                field.setAccessible(true);
                sb.append(field.getName()).append(field.get(t)).append(", ");
            }
        } catch (Exception e) {
            log.error("getTargetInfo occur error.", e);
            return "";
        }

        return sb.substring(0, sb.length() - 2) + "]";
    }

    public static Class<?> getGenericClass(Field field) {
        Type type = field.getGenericType();
        if (type instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) type;
            return (Class<?>) pt.getActualTypeArguments()[0];
        }

        return field.getType();
    }

    /**
     * Exclude specified fields and return other fields in class, but when the
     * exceptFields is empty or null just return a string array with zero length
     *
     * @param clazz
     * @param exceptFields
     * @return
     */
    public static String[] exceptFields(Class<?> clazz, String[] exceptFields) {
        if (StringUtil.isEmpty(exceptFields)) {
            return new String[]{};
        }

        Field[] fields = clazz.getDeclaredFields();

        Map<String, String> map = new HashMap<String, String>();
        for (Field field : fields) {
            map.put(field.getName(), field.getName());
        }

        for (String exceptField : exceptFields) {
            if (map.containsKey(exceptField)) {
                map.remove(exceptField);
            }
        }

        String[] includeFields = (String[]) Array.newInstance(String.class, map
                .keySet().size());
        map.keySet().toArray(includeFields);
        return includeFields;
    }

    public static Class<?>[] getClasses(String[] classes)
            throws ClassNotFoundException {
        if (!StringUtil.isEmpty(classes)) {
            List<Class<?>> list = new ArrayList<Class<?>>();
            Class<?>[] result = new Class<?>[classes.length];
            for (String clazz : classes) {
                list.add(Class.forName(clazz));
            }

            list.toArray(result);
            return result;
        }

        return null;
    }

    public static void invokeMethod(Class<?> clazz, String methodName,
                                    Class<?>[] parameterTypes, Object[] args, Object invoker)
            throws Exception {
        Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
        method.invoke(invoker, args);
    }

    public static String[] getMethodWithParamterTypes(Class<?> clazz,
                                                      Class<?>[] parameterTypes) {
        Method[] methods = clazz.getDeclaredMethods();
        List<String> methodNames = new ArrayList<String>();
        for (Method method : methods) {
            if (parameterTypesEquals(method.getParameterTypes(), parameterTypes)) {
                methodNames.add(method.getName());
            }
        }

        String[] result = (String[]) Array.newInstance(String.class,
                methodNames.size());
        methodNames.toArray(result);

        return result;
    }

    public static boolean parameterTypesEquals(
            Class<?>[] declareParameterTypes, Class<?>[] invokeParameterTypes) {
        if (declareParameterTypes.length != invokeParameterTypes.length) {
            return false;
        }

        for (int i = 0; i < declareParameterTypes.length; i++) {
            if (!declareParameterTypes[i]
                    .isAssignableFrom(invokeParameterTypes[i])) {
                return false;
            }
        }

        return true;
    }

    public static <T extends Annotation> Field getFieldWithAnnotation(
            Class<?> clazz, Class<T> annotation) {
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            T t = field.getAnnotation(annotation);
            if (t != null) {
                return field;
            }
        }

        return null;
    }

    public static <T extends Annotation> List<Field> getFieldsWithAnnotation(
            Class<?> clazz, Class<T> annotation) {
        Field[] fields = clazz.getDeclaredFields();

        List<Field> result = new ArrayList<Field>(fields.length);
        for (Field field : fields) {
            T t = field.getAnnotation(annotation);
            if (t != null) {
                result.add(field);
            }
        }

        if (result.size() != 0) {
            return result;
        }

        return null;
    }

    public static <T> void setFieldValue(T t, Field field, Object value)
            throws IllegalArgumentException, IllegalAccessException {
        field.setAccessible(true);
        field.set(t, value);
    }

    public static <T> Object getFieldValue(T t, Field field)
            throws IllegalArgumentException, IllegalAccessException {
        field.setAccessible(true);
        return field.get(t);
    }

    public static boolean isStatic(Field field) {
        if (Modifier.isStatic(field.getModifiers())) {
            return true;
        }

        return false;
    }

    public static Method[] getMethodsWithAnnotation(Class<?> clazz,
                                                    Class<? extends Annotation> annotationClass) {
        Method[] methods = clazz.getDeclaredMethods();

        List<Method> result = new ArrayList<Method>();
        for (Method method : methods) {
            Annotation annotation = method.getAnnotation(annotationClass);
            if (annotation != null) {
                result.add(method);
            }
        }

        if (!result.isEmpty()) {
            return result.toArray(new Method[result.size()]);
        }

        return new Method[0];
    }

}
